/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "lldp.h"
#include <mc/log.h>
#include <stdexcept>
#include <arpa/inet.h>

namespace dev {

// 校验响应头部字段
void lldp::validate_rsp_data(const vdpci_lldp_response& rsp) const {
    const uint32_t manufacture_id = ntohl(rsp.manufacture_id);
    if (manufacture_id != MANUFACTURE_ID || rsp.cmd_type != RSP_CMD_TYPE || rsp.sub_cmd != RSP_SUB_CMD) {
        dlog("LLDP response header mismatch: manufacture_id=${mid}, cmd_type=${cmd}, sub_cmd=${sub}",
             ("mid", manufacture_id)("cmd", rsp.cmd_type)("sub", rsp.sub_cmd));
        throw std::runtime_error("response header mismatch");
    }
}

// 检查 TLV 类型与长度是否满足范围
bool lldp::check_tlv_type_and_length(uint8_t tlv_value, uint16_t tlv_length) const {
    const auto iter = tlv_type_tlv_length_map.find(tlv_value);
    if (iter != tlv_type_tlv_length_map.end()) {
        return tlv_length >= iter->second.min && tlv_length <= iter->second.max;
    } else {
        return tlv_value >= LLDP_TLV_TYPE_RESERVED_MIN && tlv_value <= LLDP_TLV_TYPE_RESERVED_MAX;
    }
}

std::string mac_addr_to_hex_string(const uint8_t* mac_addr, size_t len) {
    std::stringstream ss;
    for (size_t i = 0; i < len; ++i) {
        if (i > 0) {
            ss << ":";
        }
        ss << std::hex << std::uppercase << std::setfill('0') << std::setw(2)
           << static_cast<unsigned>(mac_addr[i]);
    }
    return ss.str();
}

std::vector<tlv_info> lldp::parse_tlv_list(const std::vector<uint8_t>& rsp_bin, size_t data_size) const {
    std::vector<tlv_info> tlvs;
    size_t offset = 0;
    bool is_complete = false;

    while (!is_complete) {
        if (tlvs.size() >= LLDP_MAX_TLV_NUM) {
            dlog("LLDP TLV count overflow: size=${size}, max=${max}",
                 ("size", tlvs.size())("max", LLDP_MAX_TLV_NUM));
            throw std::runtime_error("received tlvs exceeds maximum tlvs counts, max: " + std::to_string(LLDP_MAX_TLV_NUM));
        }
        // 检查是否还有足够空间读取 TLV 头部（相对于 data[] 数组）
        if (offset + TLV_HEADER_LEN > data_size) {
            dlog("LLDP parse error: missing end TLV, offset=${offset}, data_size=${total}",
                 ("offset", offset)("total", data_size));
            throw std::runtime_error("did not receive end tlv");
        }
        // 从 rsp_bin[LLDP_HEADER_LEN + offset] 位置读取 TLV 头部
        const tlv_header* t_hdr = reinterpret_cast<const tlv_header*>(&rsp_bin[LLDP_HEADER_LEN + offset]);
        uint8_t header_first_byte = t_hdr->info_string_high;
        uint16_t info_string_len =
            (static_cast<uint16_t>(header_first_byte & 0x01) << 8) | t_hdr->info_string_low;
        uint8_t tlv_type = static_cast<uint8_t>(header_first_byte >> 1);
        // 解析TLV头
        if (!check_tlv_type_and_length(tlv_type, info_string_len)) {
            dlog("LLDP invalid TLV: type=${type}, len=${len}", ("type", tlv_type)("len", info_string_len));
            throw std::runtime_error("invalid tlv_type");
        }
        // 检查整个 TLV（头部+数据）是否完整
        if (offset + TLV_HEADER_LEN + info_string_len > data_size) {
            dlog("LLDP data length exceed: offset=${offset}, tlv_total_len=${len}, data_size=${total}",
                 ("offset", offset)("len", TLV_HEADER_LEN + info_string_len)("total", data_size));
            throw std::runtime_error("exceed data length");
        }
        // 读取 TLV 数据部分（从 LLDP_HEADER_LEN + offset + TLV_HEADER_LEN 开始）
        std::string info_string;
        if (info_string_len > 0) {
            info_string = std::string(
                reinterpret_cast<const char*>(&rsp_bin[LLDP_HEADER_LEN + offset + TLV_HEADER_LEN]),
                info_string_len);
        }

        tlv_info tlv = {tlv_type, info_string_len, info_string};
        tlvs.push_back(tlv);

        size_t index = tlvs.size();
        if (index <= 3 && tlv.type != static_cast<uint8_t>(index)) {
            dlog("LLDP first 3 TLVs type order error: index=${idx}, type=${type}", ("idx", index)("type", tlv.type));
            throw std::runtime_error("wrong tlv type in first 3 tlvs");
        }

        is_complete = (tlv.type == 0);
        // offset 移动到下一个 TLV：当前 TLV 的头部大小 + 数据长度
        offset += TLV_HEADER_LEN + info_string_len;
    }

    return tlvs;
}

mc::dict lldp::build_parse_result(const vdpci_lldp_response* rsp, const std::vector<tlv_info>& tlvs) const {
    // 组装结果
    const uint16_t ether_type = ntohs(rsp->ether_type);

    mc::dict result = {
        {"port_id", rsp->port_id},
        {"dest_addr", mac_addr_to_hex_string(rsp->dest_addr, 6)},
        {"src_addr", mac_addr_to_hex_string(rsp->src_addr, 6)},
        {"ether_type", ether_type},
    };

    mc::variants tlv_array;
    tlv_array.reserve(tlvs.size());
    for (const auto& t : tlvs) {       // 字段名：tlv_type, tlv_info_string
        tlv_array.push_back(mc::dict{
            {"tlv_type", t.type},
            {"info_string_len", t.info_string_len},
            {"tlv_info_string", t.info_string}
        });
    }
    result["tlvs"] = tlv_array;

    return result;
}

mc::dict lldp::parse(const std::vector<uint8_t>& rsp_bin) const {
    // 检查最小长度
    if (rsp_bin.size() < LLDP_HEADER_LEN) {
        dlog("LLDP data too short: size=${size}, required=${req}",
             ("size", rsp_bin.size())("req", LLDP_HEADER_LEN));
        throw std::runtime_error("insufficient data length");
    }
    
    const vdpci_lldp_response* rsp = reinterpret_cast<const vdpci_lldp_response*>(rsp_bin.data());
    validate_rsp_data(*rsp);

    // data 区域的总大小
    size_t data_size = rsp_bin.size() - LLDP_HEADER_LEN;
    std::vector<tlv_info> tlvs = parse_tlv_list(rsp_bin, data_size);

    return build_parse_result(rsp, tlvs);
}

std::optional<chassis_id_data> lldp::parse_chassis_id_tlv(const std::string& info_string) const {
    if (info_string.empty()) {
        return std::nullopt;
    }

    uint8_t subtype = static_cast<uint8_t>(info_string[0]);
    if (chassis_id_name_switch.find(static_cast<chassis_id_subtype>(subtype)) == chassis_id_name_switch.end()) {
        throw std::runtime_error("chassis_id_name_switch not match");
    }
    std::string info_string_data(info_string.begin() + 1, info_string.end());

    const auto chassis_st = static_cast<chassis_id_subtype>(subtype);
    if (chassis_st == chassis_id_subtype::MAC_ADDRESS) {
        const std::string mac = mac_addr_to_hex_string(reinterpret_cast<const uint8_t*>(info_string_data.data()), 6);
        return chassis_id_data{mac, chassis_id_name_switch.at(chassis_st)};
    }

    if (chassis_st == chassis_id_subtype::NETWORK_ADDRESS) {
        if (info_string_data.size() >= 6) {
            uint8_t addr_len = static_cast<uint8_t>(info_string_data[0]);
            uint8_t network_subtype = static_cast<uint8_t>(info_string_data[1]);
            if (network_subtype == 0 && addr_len == 5) {
                const uint8_t* ip_bytes = reinterpret_cast<const uint8_t*>(info_string_data.data() + 2);
                char buf[16];
                std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]);
                return chassis_id_data{std::string(buf), chassis_id_name_switch.at(chassis_st)};
            }
        }
        return chassis_id_data{info_string_data, chassis_id_name_switch.at(chassis_st)};
    }

    return chassis_id_data{info_string_data, chassis_id_name_switch.at(chassis_st)};
}

std::optional<port_id_data> lldp::parse_port_id_tlv(const std::string& info_string) const {
    if (info_string.empty()) {
        return std::nullopt;
    }

    uint8_t subtype = static_cast<uint8_t>(info_string[0]);
    if (port_id_name_switch.find(static_cast<port_id_subtype>(subtype)) == port_id_name_switch.end()) {
        throw std::runtime_error("port_id_name_switch not match");
    }
    std::string info_string_data(info_string.begin() + 1, info_string.end());

    const auto port_st = static_cast<port_id_subtype>(subtype);
    if (port_st == port_id_subtype::MAC_ADDRESS) {
        const std::string mac = mac_addr_to_hex_string(reinterpret_cast<const uint8_t*>(info_string_data.data()), 6);
        return port_id_data{mac, port_id_name_switch.at(port_st)};
    }

    if (port_st == port_id_subtype::NETWORK_ADDRESS) {
        if (info_string_data.size() >= 6) {
            uint8_t addr_len = static_cast<uint8_t>(info_string_data[0]);
            uint8_t network_subtype = static_cast<uint8_t>(info_string_data[1]);
            if (network_subtype == 0 && addr_len == 5) {
                const uint8_t* ip_bytes = reinterpret_cast<const uint8_t*>(info_string_data.data() + 2);
                char buf[16];
                std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]);
                return port_id_data{std::string(buf), port_id_name_switch.at(port_st)};
            }
        }
        return port_id_data{info_string_data, port_id_name_switch.at(port_st)};
    }

    return port_id_data{info_string_data, port_id_name_switch.at(port_st)};
}

std::optional<ttl_data> lldp::parse_ttl_tlv(const std::string& info_string) const {
    if (info_string.size() < 2) {
        return std::nullopt;
    }
    ttl_data result;
    result.ttl = static_cast<uint16_t>((static_cast<uint8_t>(info_string[0]) << 8) |
                                       static_cast<uint8_t>(info_string[1]));
    return result;
}

std::optional<system_name_data> lldp::parse_system_name_data_tlv(const std::string& info_string) const {
    system_name_data result;
    if (info_string.size() > 512) {
        result.system_name = info_string.substr(0, 512);
    } else {
        result.system_name = info_string;
    }
    return result;
}
    
std::optional<org_specific_data> lldp::parse_org_specific_data_tlv(const std::string& info_string) const {
    if (info_string.size() < 4) {
        return std::nullopt;
    }
    if (info_string.size() < 3 || info_string.substr(0, 3) != org_oui_byte) {
        return std::nullopt;
    }
    uint8_t subtype = static_cast<uint8_t>(info_string[3]);
    org_specific_data result;

    if (subtype == static_cast<uint8_t>(IEEE_subtype::PORT_VLAN_ID)) {
        if (info_string.size() < 6) {
            return std::nullopt;
        }
        uint16_t vlan_id = static_cast<uint16_t>((static_cast<uint8_t>(info_string[4]) << 8) |
                                                 static_cast<uint8_t>(info_string[5]));
        if (vlan_id == 0) {
            vlan_id = 0xFFFF;
        }
        result.vlan_id = vlan_id;
        return result;
    }
    return std::nullopt;
}

mc::dict lldp::parse_lldp_tlv(const lldp_tlv& tlv) const {
    mc::dict result;

    if (tlv.tlv_info_string.empty()) {
        return result;
    }

    // 根据TLV类型解析，并将所有字段放入字典
    if (tlv.type == tlv_type::CHASSIS_ID_TLV) {
        if (auto data = parse_chassis_id_tlv(tlv.tlv_info_string)) {
            result["chassis_id"] = data->chassis_id;
            result["chassis_id_subtype"] = data->chassis_id_subtype;
        }
    } else if (tlv.type == tlv_type::PORT_ID_TLV) {
        if (auto data = parse_port_id_tlv(tlv.tlv_info_string)) {
            result["port_id"] = data->port_id;
            result["port_id_subtype"] = data->port_id_subtype;
        }
    } else if (tlv.type == tlv_type::TTL_TLV) {
        if (auto data = parse_ttl_tlv(tlv.tlv_info_string)) {
            result["ttl"] = data->ttl;
        }
    } else if (tlv.type == tlv_type::SYSTEM_NAME_TLV) {
        if (auto data = parse_system_name_data_tlv(tlv.tlv_info_string)) {
            result["system_name"] = data->system_name;
        }
    } else if (tlv.type == tlv_type::ORG_SPECIFIC_TLV) {
        if (auto data = parse_org_specific_data_tlv(tlv.tlv_info_string)) {
            if (data->vlan_id.has_value()) {
                result["vlan_id"] = data->vlan_id.value();
            }
        }
    }

    return result;
}

} // namespace dev